﻿/*
   Copyright 2012 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;

namespace Flower.Actors
{
    public class PriorityQueue<T> : IProducerConsumerCollection<T>
    {
        private readonly ArrayHeap<T> _heap;
        private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

        public PriorityQueue(IComparer<T> comparer)
        {
            _heap = new ArrayHeap<T>(comparer);
        }

        public void CopyTo(T[] array, int index)
        {
            _lock.EnterReadLock();
            try
            {
                for (int i = 0, j = index; i < _heap.Count; i++, j++)
                {
                    array[j] = _heap[i];
                }
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        public T[] ToArray()
        {
            _lock.EnterReadLock();
            try
            {
                var result = new T[_heap.Count];

                for (int i = 0; i < result.Length; i++)
                {
                    result[i] = _heap[i];
                }

                return result;
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        public bool TryAdd(T item)
        {
            _lock.EnterWriteLock();
            try
            {
                _heap.Add(item);
                return true;
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }

        public bool TryTake(out T item)
        {
            _lock.EnterWriteLock();
            try
            {
                if (_heap.Count == 0)
                {
                    item = default(T);
                    return false;
                }

                item = _heap.Pop();
                return true;
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }

        public IEnumerator<T> GetEnumerator()
        {
            throw new NotSupportedException();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            throw new NotSupportedException();
        }

        public void CopyTo(Array array, int index)
        {
            _lock.EnterReadLock();
            try
            {
                _heap.CopyTo(array, index);
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        public int Count
        {
            get 
            { 
                _lock.EnterReadLock();
                try
                {
                    return _heap.Count;
                }
                finally
                {
                    _lock.ExitReadLock();
                }
            }
        }

        public bool IsSynchronized
        {
            get { return true; }
        }

        public object SyncRoot
        {
            get { return this; }
        }

        internal T[] Steal(double amount)
        {
            if (amount < 0 || amount > 1) throw new ArgumentException("Amount must be between 0 and 1.", "amount");

            _lock.EnterWriteLock();
            try
            {
                var itemsToSteal = (int)Math.Floor(_heap.Count * amount);

                if (itemsToSteal > 0) 
                {
                    return _heap.Steal(itemsToSteal);
                }

                return new T[0];
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }
    }
}
